﻿using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

namespace Purdue.Extensions
{
    public static class ListBoxExtensions
    {
        private static MethodInfo setSelectedItemsMethod = typeof(ListBox).GetMethod("SetSelectedItems", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        private static PropertyInfo getItemsHostProperty = typeof(ItemsControl).GetProperty("ItemsHost", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        private static MethodInfo bringIndexIntoViewMethod = typeof(VirtualizingPanel).GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        /// <summary>
        /// Wrapper to execute the protected method SetSelectedItems.
        /// </summary>
        /// <param name="firstItemIndex">The first item index to select.</param>
        /// <param name="lastItemIndex">The last item index to select.</param>
        /// <exception cref="MethodInfo.Invoke">That method is used internally and no exception is handled by this method.</exception>
        /// <returns></returns>
        public static bool SetSelectedItemRange(this ListBox listBox, int firstItemIndex, int lastItemIndex)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            IEnumerable items = listBox.Items.Cast<object>().Skip(firstItemIndex).Take(lastItemIndex - firstItemIndex + 1).ToList();
            return (bool)setSelectedItemsMethod.Invoke(listBox, new object[] { items });
        }

        /// <summary>
        /// Wrapper to execute the protected method SetSelectedItems. Use this to explicitly select a set of items
        /// since calling SelectedItems.Add many times is very slow!
        /// </summary>
        /// <param name="items">The collection of items to select.</param>
        /// <exception cref="MethodInfo.Invoke">That method is used internally and no exception is handled by this method.</exception>
        /// <returns></returns>
        public static bool SetSelectedItemCollection(this ListBox listBox, IEnumerable items)
        {
            return (bool)setSelectedItemsMethod.Invoke(listBox, new object[] { items });
        }

        /// <summary>
        /// Wrapper to replace the ListBox.ScrollIntoView that does not work with virtualized grouped panels.
        /// </summary>
        /// <param name="listBox"></param>
        /// <param name="itemIndex"></param>
        public static void ScrollIndexIntoView(this ListBox listBox, int itemIndex)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            if (listBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                InternalScrollIndexIntoView(listBox, itemIndex);
            }
            else
            {
                listBox.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => InternalScrollIndexIntoView(listBox, itemIndex)));
            }
        }

        private static void InternalScrollIndexIntoView(ListBox listBox, int itemIndex)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            if ((listBox.Items != null) && (itemIndex >= 0) && (itemIndex < listBox.Items.Count))
            {
                VirtualizingPanel itemsHost = getItemsHostProperty.GetValue(listBox, null) as VirtualizingPanel;
                if (itemsHost != null)
                {
                    bringIndexIntoViewMethod.Invoke(itemsHost, new object[] { itemIndex });
                }
                else
                {
                    listBox.ScrollIntoView(listBox.Items[itemIndex]);
                }
            }
        }

        public static bool HasItems(this ListBox listBox)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            return (listBox.Items != null && listBox.Items.Count > 0);
        }

        public static bool HasGroups(this ListBox listBox)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            return (listBox.HasItems() && listBox.Items.Groups != null && listBox.Items.Groups.Count > 0);
        }

        public static bool IsValidItemIndex(this ListBox listBox, int itemIndex)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            return listBox.HasItems() && listBox.Items.IsValidIndex(itemIndex);
        }

        public static bool IsValidGroupIndex(this ListBox listBox, int groupIndex)
        {
            if (listBox == null)
                throw new ArgumentNullException("listBox");

            return listBox.HasGroups() && listBox.Items.Groups.IsValidIndex(groupIndex);
        }
    }
}
